import {describe} from 'mocha'
import { IGitSourceParams } from '../src/git-source-params';
import Sinon from 'sinon'
import * as fs from 'fs'
import * as path from 'path'
import proxyquire from 'proxyquire'
import { expect } from 'chai'

describe('PackagesSource', () => {
    let sourceParams = {} as IGitSourceParams
    sourceParams.order = 1
    sourceParams.projectDir = 'test-project'
    sourceParams.token = 'Basic NjJjM2EzYTg5Y2NiMWI2YmY5NDgxODc4Ok51SG0yMmpqYjRYZw=='
    sourceParams.repoUrl = 'http://123.57.43.139'
    sourceParams.orgId = '60d54f3daccf2bbd6659f3ad'
    sourceParams.repoType = 'generic'
    sourceParams.repoId = 'flow_generic_repo'
    sourceParams.artifactName = 'Artifacts_2694268'
    sourceParams.versionName = '2023-11-29-14-58-06'
    sourceParams.user = '62c3a3a89ccb1b6bf9481878'
    sourceParams.packagesDownloadUrl = 'http://example.com/download'

    let sourceParamsWithPath = {} as IGitSourceParams
    sourceParamsWithPath.order = 1
    sourceParamsWithPath.projectDir = 'test-project'
    sourceParamsWithPath.token = 'Basic NjJjM2EzYTg5Y2NiMWI2YmY5NDgxODc4Ok51SG0yMmpqYjRYZw=='
    sourceParamsWithPath.repoUrl = 'http://123.57.43.139'
    sourceParamsWithPath.orgId = '60d54f3daccf2bbd6659f3ad'
    sourceParamsWithPath.repoType = 'generic'
    sourceParamsWithPath.repoId = 'flow_generic_repo'
    sourceParamsWithPath.artifactName = 'demo/Artifacts_2694268'
    sourceParamsWithPath.versionName = '2023-11-29-14-58-06'
    sourceParamsWithPath.user = '62c3a3a89ccb1b6bf9481878'
    sourceParamsWithPath.packagesDownloadUrl = 'http://example.com/download'

    let sourceParamsWithPackagesFilePath = {} as IGitSourceParams
    sourceParamsWithPackagesFilePath.order = 1
    sourceParamsWithPackagesFilePath.projectDir = 'test-project'
    sourceParamsWithPackagesFilePath.token = 'Basic NjJjM2EzYTg5Y2NiMWI2YmY5NDgxODc4Ok51SG0yMmpqYjRYZw=='
    sourceParamsWithPackagesFilePath.repoUrl = 'http://123.57.43.139'
    sourceParamsWithPackagesFilePath.orgId = '60d54f3daccf2bbd6659f3ad'
    sourceParamsWithPackagesFilePath.repoType = 'generic'
    sourceParamsWithPackagesFilePath.repoId = 'flow_generic_repo'
    sourceParamsWithPackagesFilePath.artifactName = 'Artifacts_2694268'
    sourceParamsWithPackagesFilePath.versionName = '2023-11-29-14-58-06'
    sourceParamsWithPackagesFilePath.user = '62c3a3a89ccb1b6bf9481878'
    sourceParamsWithPackagesFilePath.packagesDownloadUrl = 'http://example.com/download'
    sourceParamsWithPackagesFilePath.packagesFilePath = 'custom/path/artifact.tgz'

    let PackagesSource: any;
    let stepInfoSpy: sinon.SinonSpy<any[], any>;
    let stepAddOutputSpy: sinon.SinonSpy<any[], any>;
    let stepExportEnvVarSpy: sinon.SinonSpy<any[], any>;
    let ensureDirectoryExistsStub: sinon.SinonStub;
    let utilClearDirStub: sinon.SinonStub;

    beforeEach(function () {
        //创建 spy 对象，
        stepInfoSpy = Sinon.spy();
        stepAddOutputSpy = Sinon.spy();
        stepExportEnvVarSpy = Sinon.spy();
        ensureDirectoryExistsStub = Sinon.stub();
        utilClearDirStub = Sinon.stub();

        //proxyquire 替换 packages-source中依赖的 step 模块, stepInfoSpy、stepAddOutputSpy 和 stepExportEnvVarSpy 作为替代模块的方法传给 proxyquire
        PackagesSource = proxyquire('../src/packages-source', {
            '@flow-step/step-toolkit': {
              info: stepInfoSpy,
              addOutput: stepAddOutputSpy,
              exportEnvVar: stepExportEnvVarSpy
            },
            './util': {
                clearDir: utilClearDirStub,
                ensureDirectoryExists: ensureDirectoryExistsStub
            }
          }).PackagesSource;
    })

    afterEach(() => {
        Sinon.restore();
    })

    describe('downloadPackages', () => {
        it('should download packages successfully without packagesFilePath', async () => {
            let response = {
                data: Buffer.from('file content'),
                status: 200
            }
            const axiosStub = Sinon.stub().resolves(response)
            const fsExistsSyncStub = Sinon.stub().returns(false);
            const fsMkdirSyncStub = Sinon.stub();
            const fsWriteFileSyncStub = Sinon.stub();

            const controller = proxyquire('../src/packages-source', {
                axios: axiosStub,
                fs: {
                    existsSync: fsExistsSyncStub,
                    mkdirSync: fsMkdirSyncStub,
                    writeFileSync: fsWriteFileSyncStub
                },
                '@flow-step/step-toolkit': {
                    info: stepInfoSpy,
                    addOutput: stepAddOutputSpy,
                    exportEnvVar: stepExportEnvVarSpy
                },
                './util': {
                    clearDir: utilClearDirStub,
                    ensureDirectoryExists: ensureDirectoryExistsStub
                }
            }).PackagesSource

            const packagesSource = new controller();
            await packagesSource.downloadPackages(sourceParams);

            // 验证目录创建
            Sinon.assert.calledWith(fsMkdirSyncStub, sourceParams.projectDir);

            // 验证 axios 调用
            Sinon.assert.calledWith(axiosStub, Sinon.match({
                method: 'get',
                url: `http://123.57.43.139/api/protocol/60d54f3daccf2bbd6659f3ad/generic/flow_generic_repo/files/Artifacts_2694268?signUrl=true&userId=62c3a3a89ccb1b6bf9481878&version=2023-11-29-14-58-06`,
                headers: {
                    Authorization: `Basic NjJjM2EzYTg5Y2NiMWI2YmY5NDgxODc4Ok51SG0yMmpqYjRYZw==`
                },
                responseType: 'arraybuffer'
            }));

            // 验证文件写入
            const expectedFilePath = path.join(sourceParams.projectDir, `${sourceParams.artifactName}_${sourceParams.versionName}.tgz`);
            Sinon.assert.calledWith(fsWriteFileSyncStub, expectedFilePath, response.data);
        })

        it('should download packages successfully with packagesFilePath', async () => {
            let response = {
                data: Buffer.from('file content'),
                status: 200
            }
            const axiosStub = Sinon.stub().resolves(response)
            const fsExistsSyncStub = Sinon.stub().returns(false);
            const fsMkdirSyncStub = Sinon.stub();
            const fsWriteFileSyncStub = Sinon.stub();

            const controller = proxyquire('../src/packages-source', {
                axios: axiosStub,
                fs: {
                    existsSync: fsExistsSyncStub,
                    mkdirSync: fsMkdirSyncStub,
                    writeFileSync: fsWriteFileSyncStub
                },
                '@flow-step/step-toolkit': {
                    info: stepInfoSpy,
                    addOutput: stepAddOutputSpy,
                    exportEnvVar: stepExportEnvVarSpy
                },
                './util': {
                    clearDir: utilClearDirStub,
                    ensureDirectoryExists: ensureDirectoryExistsStub
                }
            }).PackagesSource

            const packagesSource = new controller();
            await packagesSource.downloadPackages(sourceParamsWithPackagesFilePath);

            // 验证目录创建逻辑被调用
            Sinon.assert.calledOnce(ensureDirectoryExistsStub);

            // 验证文件写入到自定义路径
            const expectedFilePath = path.join(sourceParamsWithPackagesFilePath.projectDir, 'custom/path/artifact.tgz');
            Sinon.assert.calledWith(fsWriteFileSyncStub, expectedFilePath, response.data);
        })

        it('should create directory when artifactName contains path separator', async () => {
            let response = {
                data: Buffer.from('file content'),
                status: 200
            }
            const axiosStub = Sinon.stub().resolves(response)
            const fsExistsSyncStub = Sinon.stub().returns(false);
            const fsMkdirSyncStub = Sinon.stub();
            const fsWriteFileSyncStub = Sinon.stub();

            const controller = proxyquire('../src/packages-source', {
                axios: axiosStub,
                fs: {
                    existsSync: fsExistsSyncStub,
                    mkdirSync: fsMkdirSyncStub,
                    writeFileSync: fsWriteFileSyncStub
                },
                '@flow-step/step-toolkit': {
                    info: stepInfoSpy,
                    addOutput: stepAddOutputSpy,
                    exportEnvVar: stepExportEnvVarSpy
                },
                './util': {
                    clearDir: utilClearDirStub,
                    ensureDirectoryExists: ensureDirectoryExistsStub
                }
            }).PackagesSource

            const packagesSource = new controller();
            await packagesSource.downloadPackages(sourceParamsWithPath);

            // 验证目录创建逻辑被调用
            Sinon.assert.calledOnce(ensureDirectoryExistsStub);

            // 验证 axios 调用
            Sinon.assert.calledWith(axiosStub, Sinon.match({
                method: 'get',
                url: `http://123.57.43.139/api/protocol/60d54f3daccf2bbd6659f3ad/generic/flow_generic_repo/files/demo/Artifacts_2694268?signUrl=true&userId=62c3a3a89ccb1b6bf9481878&version=2023-11-29-14-58-06`,
                headers: {
                    Authorization: `Basic NjJjM2EzYTg5Y2NiMWI2YmY5NDgxODc4Ok51SG0yMmpqYjRYZw==`
                }
            }));
        })

        it('should clear existing directory', async () => {
            let response = {
                data: Buffer.from('file content'),
                status: 200
            }
            const axiosStub = Sinon.stub().resolves(response)
            const fsExistsSyncStub = Sinon.stub().returns(true);
            const fsMkdirSyncStub = Sinon.stub();
            const fsWriteFileSyncStub = Sinon.stub();

            const controller = proxyquire('../src/packages-source', {
                axios: axiosStub,
                fs: {
                    existsSync: fsExistsSyncStub,
                    mkdirSync: fsMkdirSyncStub,
                    writeFileSync: fsWriteFileSyncStub
                },
                '@flow-step/step-toolkit': {
                    info: stepInfoSpy,
                    addOutput: stepAddOutputSpy,
                    exportEnvVar: stepExportEnvVarSpy
                },
                './util': {
                    clearDir: utilClearDirStub,
                    ensureDirectoryExists: ensureDirectoryExistsStub
                }
            }).PackagesSource

            const packagesSource = new controller();
            await packagesSource.downloadPackages(sourceParams);

            // 验证清理目录被调用
            Sinon.assert.calledWith(utilClearDirStub, sourceParams.projectDir);
        })

        it('should handle download error', async () => {
            const error = new Error('Network error') as any;
            error.response = {
                data: 'Error details'
            };
            const axiosStub = Sinon.stub().rejects(error);
            const fsExistsSyncStub = Sinon.stub().returns(false);
            const fsMkdirSyncStub = Sinon.stub();
            const fsWriteFileSyncStub = Sinon.stub();

            const controller = proxyquire('../src/packages-source', {
                axios: axiosStub,
                fs: {
                    existsSync: fsExistsSyncStub,
                    mkdirSync: fsMkdirSyncStub,
                    writeFileSync: fsWriteFileSyncStub
                },
                '@flow-step/step-toolkit': {
                    info: stepInfoSpy,
                    addOutput: stepAddOutputSpy,
                    exportEnvVar: stepExportEnvVarSpy
                },
                './util': {
                    clearDir: utilClearDirStub,
                    ensureDirectoryExists: ensureDirectoryExistsStub
                }
            }).PackagesSource

            const packagesSource = new controller();

            try {
                await packagesSource.downloadPackages(sourceParams);
                expect.fail('Should have thrown an error');
            } catch (err: any) {
                expect(err.message).to.equal('Download artifact failed!');
            }

            // 验证错误信息被记录
            Sinon.assert.calledWith(stepInfoSpy, 'Network error');
            Sinon.assert.calledWith(stepInfoSpy, 'Error details');
        })

        it('should handle packagesFilePath starting with slash', async () => {
            let response = {
                data: Buffer.from('file content'),
                status: 200
            }
            const axiosStub = Sinon.stub().resolves(response)
            const fsExistsSyncStub = Sinon.stub().returns(false);
            const fsMkdirSyncStub = Sinon.stub();
            const fsWriteFileSyncStub = Sinon.stub();

            const paramsWithSlash = { ...sourceParamsWithPackagesFilePath };
            paramsWithSlash.packagesFilePath = '/custom/path/artifact.tgz';

            const controller = proxyquire('../src/packages-source', {
                axios: axiosStub,
                fs: {
                    existsSync: fsExistsSyncStub,
                    mkdirSync: fsMkdirSyncStub,
                    writeFileSync: fsWriteFileSyncStub
                },
                '@flow-step/step-toolkit': {
                    info: stepInfoSpy,
                    addOutput: stepAddOutputSpy,
                    exportEnvVar: stepExportEnvVarSpy
                },
                './util': {
                    clearDir: utilClearDirStub,
                    ensureDirectoryExists: ensureDirectoryExistsStub
                }
            }).PackagesSource

            const packagesSource = new controller();
            await packagesSource.downloadPackages(paramsWithSlash);

            // 验证路径处理正确（去掉开头的斜杠）
            const expectedFilePath = path.join(paramsWithSlash.projectDir, 'custom/path/artifact.tgz');
            Sinon.assert.calledWith(fsWriteFileSyncStub, expectedFilePath, response.data);
        })
    })

    describe('writePackagesInfo', () => {
        it('should write packages info correctly', async () => {
            const packagesSource = new PackagesSource();

            await packagesSource.writePackagesInfo(sourceParams);

            //验证 spy 对象是否以预期的参数被调用
            Sinon.assert.calledWith(stepInfoSpy, 'Write packages info')
            Sinon.assert.calledWith(stepAddOutputSpy, `outputsToEnv.CI_SOURCE_NAME=${sourceParams.artifactName}`)
            Sinon.assert.calledWith(stepAddOutputSpy, `outputsToEnv.CI_SOURCE_NAME_${sourceParams.order}=${sourceParams.artifactName}`)
            Sinon.assert.calledWith(stepAddOutputSpy, `outputsToEnv.CI_SOURCE_URL=${sourceParams.packagesDownloadUrl}`)
            Sinon.assert.calledWith(stepAddOutputSpy, `outputsToEnv.CI_SOURCE_URL_${sourceParams.order}=${sourceParams.packagesDownloadUrl}`)
            Sinon.assert.calledWith(stepAddOutputSpy, `outputsToEnv.CI_VERSION_NAME=${sourceParams.versionName}`)
            Sinon.assert.calledWith(stepAddOutputSpy, `outputsToEnv.CI_VERSION_NAME_${sourceParams.order}=${sourceParams.versionName}`)

            Sinon.assert.calledWith(stepExportEnvVarSpy, 'CI_SOURCE_NAME', sourceParams.artifactName)
            Sinon.assert.calledWith(stepExportEnvVarSpy, `CI_SOURCE_NAME_${sourceParams.order}`, sourceParams.artifactName)
            Sinon.assert.calledWith(stepExportEnvVarSpy, 'CI_SOURCE_URL', sourceParams.packagesDownloadUrl)
            Sinon.assert.calledWith(stepExportEnvVarSpy, `CI_SOURCE_URL_${sourceParams.order}`, sourceParams.packagesDownloadUrl)
            Sinon.assert.calledWith(stepExportEnvVarSpy, 'CI_VERSION_NAME', sourceParams.versionName)
            Sinon.assert.calledWith(stepExportEnvVarSpy, `CI_VERSION_NAME_${sourceParams.order}`, sourceParams.versionName)
        })
    })

    describe('handlePackagesSource', () => {
        it('should handle packages source workflow', async () => {
            let response = {
                data: Buffer.from('file content'),
                status: 200
            }
            const axiosStub = Sinon.stub().resolves(response)
            const fsExistsSyncStub = Sinon.stub().returns(false);
            const fsMkdirSyncStub = Sinon.stub();
            const fsWriteFileSyncStub = Sinon.stub();

            const controller = proxyquire('../src/packages-source', {
                axios: axiosStub,
                fs: {
                    existsSync: fsExistsSyncStub,
                    mkdirSync: fsMkdirSyncStub,
                    writeFileSync: fsWriteFileSyncStub
                },
                '@flow-step/step-toolkit': {
                    info: stepInfoSpy,
                    addOutput: stepAddOutputSpy,
                    exportEnvVar: stepExportEnvVarSpy
                },
                './util': {
                    clearDir: utilClearDirStub,
                    ensureDirectoryExists: ensureDirectoryExistsStub
                }
            }).PackagesSource

            const packagesSource = new controller();
            await packagesSource.handlePackagesSource(sourceParams);

            // 验证下载和写入信息都被调用
            Sinon.assert.calledWith(stepInfoSpy, 'Use Packages as source');
            Sinon.assert.calledWith(stepInfoSpy, 'Write packages info');
            Sinon.assert.called(axiosStub);
            Sinon.assert.called(stepAddOutputSpy);
            Sinon.assert.called(stepExportEnvVarSpy);
        })
    })
})